Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

overlord/snapstate: do a minimal sanity check on containers #4464

Merged
merged 6 commits into from Jan 18, 2018

Conversation

chipaca
Copy link
Contributor

@chipaca chipaca commented Jan 10, 2018

This introduces validateContainer, that uses snap.Container's
Walk to do a minimal sanity check of the container in question.

With this, a snap that has an obvious problem due to e.g. a missing or
non-executable app, or an unreadable snap.yaml, will fail to
install. The error message will alert the user to contact the snap
developers, and the system's log will contain additional details meant
for the developers themselves.

@chipaca chipaca force-pushed the snap-permission-sanity-check branch from bd52a4f to ffcbee1 Compare January 10, 2018 09:07
@chipaca chipaca changed the title overlord/snapstate: add validateContainer, that uses snap.Container's overlord/snapstate: do a minimal sanity check onn containers Jan 10, 2018
This introduces validateContainer, that uses snap.Container's
Walk to do a minimal sanity check of the container in question.

With this, a snap that has an obvious problem due to e.g. a missing or
non-executable app, or an unreadable snap.yaml, will fail to
install. The error message will alert the user to contact the snap
developers, and the system's log will contain additional details meant
for the developers themselves.
@chipaca chipaca force-pushed the snap-permission-sanity-check branch from ffcbee1 to 91b4232 Compare January 10, 2018 09:08
@chipaca chipaca changed the title overlord/snapstate: do a minimal sanity check onn containers overlord/snapstate: do a minimal sanity check on containers Jan 10, 2018
Copy link
Contributor

@mvo5 mvo5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks for working on this. I put some ideas/suggestions inline. I wonder if we should add a tiny (i.e. not testing everything that was already unit tested, just one broken test snap and we try to install it) spread test as well? This way we could check that the logging does what we expect it does.


var files [][]string
for _, app := range info.Apps {
files = append(files, []string{app.Command, ""})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pardon my ignorance, but why an extra empty string ("") here?

Copy link
Contributor Author

@chipaca chipaca Jan 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the last argument of MakeTestSnapWithFiles is a [][]string, with the inner list's first element being the filename, and the second being its contents

}

path = strings.TrimPrefix(filepath.Clean(path), "/")
if strings.HasPrefix(path, "../") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, some questions: when does this condition happens? And if we don't handle it, is normPath() as a name maybe a bit too generic? I.e. if I call normPath("../foo") the result "" is a bit unexpected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can create a snap with an app whose command is ../../../bin/sh, in which case there's nothing for the checker to check.
normPath signals "nothing to do" with the empty string.
maybe i should add a comment :-)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we had a bit of logic that reconstructs the real path knowing two bits of information:

  1. classic flag for the snap
  2. effective mount dir for the snap (which depends on 1)

Then we can construct an absolute path by normalizing whatever relative paths we get here and complete the validation.

c.Check(err, Equals, snapstate.ErrMissingPaths)
}

func (s *checkSnapSuite) TestValidateContainerSnapYamlBadPermsFails(c *C) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pardon my ignorance again, but how is this different from TestValidateContainerEmptyButBadPermFails ? Looks quite similar to me, maybe a comment that highlights the differences? For if it is about permissions of "." vs "meta" vs "meta/snap.yaml" - maybe make it table driven?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll take a look

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while the error and yaml could be tabled, the main differences are in the little setups. Maybe I should add a comment explaining what it's doing? highlighting differences


var (
ErrBadModes = errors.New("snap is unusable due to bad permissions; contact develper")
ErrMissingPaths = errors.New("snap is unusable due to missing files; contact developr")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo (will fix)

// Lstat(), and guard against loops, and ... huge can of
// worms, and as this validator is meant as a developer aid
// more than anything else, not worth it IMHO (as I can't
// imagine this happening by accident).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH it would be nice to cover a scenario when app.Command symlink is dangling.

Simplistic resolving can be done like this: https://play.golang.org/p/oDwwA5x6lBo Then provide something like snap.Container.ResolveSymlink() which in turn would call unsquashfs -ll <snap> <path>, grab the exact path and the target, accumulate those in walk function as these files may still appear when walking and if they don't have a separate WalkPaths(paths []string, walkfunc) which calls unsquashfs -ll <snap> <paths>. Just a thought, though it looks like quite some work.

Copy link
Contributor Author

@chipaca chipaca Jan 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem I don't want to address (and tried to point out in the comment) is that a symlink in a squashfs snap can look like

lrwxrwxrwx john/john                10 2018-01-11 08:11 ./baz -> qux -> foo -> bar

in -lls; I'd have to do two calls, one for the above (to get the target) and one to just -ls to get the filename, and match them up. Much too error prone for what it's gaining us.

Yes, it would be nicer. I don't think it's worth it (at least for now).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realised I need to do something about this anyway. Augh.

@chipaca
Copy link
Contributor Author

chipaca commented Jan 11, 2018

in the spread tests I use snap try so this test still works once snap pack fails

Copy link
Collaborator

@bboozzoo bboozzoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for fixes

@chipaca chipaca force-pushed the snap-permission-sanity-check branch from df27ee1 to 3328b78 Compare January 12, 2018 09:23

if needsrx[path] || mode.IsDir() {
if mode.Perm()&0555 != 0555 {
logger.Noticef("in snap %q: %q should be world-readable and -executable, and isn't: %s", s.Name(), path, mode)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...world-readable and executable...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough (done)

if idx < 0 {
return nil, errBadPath(raw)
}
st.path = st.path[:idx]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're so close to resolving the symlinks now. :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random thought:

// package squashfs
type Readlinker {
    Readlink() (string, err)
}

func (s stat) Sys() { return *s }
func (s *stat) Readlink() {
     if st.Mode & os.ModeSymlink { return s.linksTo, nil }
     return "", errors.New("not a symlink")
}

Then while walking:

err := c.Walk(".", func(path string, info os.FileInfo, err error) error {
     ...
     if info.Mode() & os.ModeSymlink {
         if rd, _ := info.Sys().(squashfs.Readlinker); rd != nil {
           whereTo, err := rd.Readlink()
           ...
         }
    }
    ....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd have to be something like adding Readlink to the Container interface, and have snapdir and squashfs both implement it, but yeah, something like that is doable. With the existing "have -> in a filename and watch it being hilarious" caveat.

As I said on IRC, if we want to do that I think it's followup material, not for this initial PR (which has already stagnated enough)

@codecov-io
Copy link

Codecov Report

❗ No coverage uploaded for pull request base (master@987ab47). Click here to learn what that means.
The diff coverage is 90.09%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #4464   +/-   ##
=========================================
  Coverage          ?   78.19%           
=========================================
  Files             ?      453           
  Lines             ?    32058           
  Branches          ?        0           
=========================================
  Hits              ?    25067           
  Misses            ?     4915           
  Partials          ?     2076
Impacted Files Coverage Δ
cmd/snap-exec/main.go 71.42% <ø> (ø)
overlord/snapstate/snapstate.go 82.16% <33.33%> (ø)
snap/squashfs/stat.go 97.34% <60%> (ø)
overlord/snapstate/check_snap.go 86.25% <93.54%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 987ab47...2f7a8ea. Read the comment docs.

tr -s "\n " " " < error.log | MATCH 'contact developer'

# give things time to reach the journal
sleep 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick) you could write a for i in $(seq 120); do if jounralctl -u snapd | grep check_snap; then break; fi; sleep 0.2; done here but probably not worth it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, I see this was already removed.

@mvo5 mvo5 merged commit 68f9d1c into snapcore:master Jan 18, 2018
@chipaca chipaca deleted the snap-permission-sanity-check branch February 7, 2018 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants